/* Copyright (c) 2011 Timothée Lacroix, Alexandre Défossez
 * This file is part of HINAPT.
 *
 * HINAPT is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * HINAPT is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with HINAPT.  If not, see <http://www.gnu.org/licenses/>.
 */


#include "classifier.hpp"

void ClassifierTh::print(){
  for(int i=0;i<(int)features.size();i++){
    cout<<features[i].f.numero<<" "<<features[i].f.x1
                              <<" "<<features[i].f.y1
                              <<" "<<features[i].f.x2
                              <<" "<<features[i].f.y2
                              <<" // "<<features[i].threshold
                              <<" "<<features[i].inf
                              <<" /// "<<Fweights[i]<<endl;
  }
}

bool ClassifierTh::load(istream &input){
  int size;
  input>>size;
  input >> threshold;
  for(int i=0;i<size;i++){
    double fw;
    input>>fw;
    Fweights.push_back(fw);
    ThFeature b;
    if(!b.load(input))
      return false;
    features.push_back(b);
  }
  return !(input.fail());
}

bool ClassifierTh::save(ostream &output){
  output<<(int)features.size()<<" " << threshold << " ";
  for(int i=0;i<(int)features.size();i++){
    output<<Fweights[i]<<" ";
    features[i].save(output);
  }
  return true;
}


int ClassifierTh::eval(Mat img,double weight){
  double total=0;
  for(int i=0;i<(int)features.size();i++){
    double val=(features[i].eval(img,weight)==1)?1:0;
    total+=val*Fweights[i];
  }
  return (total-threshold>=0)?1:-1;
}

double ClassifierTh::evaluer(Mat img){
  double total=0;
  for(int i=0;i<(int)features.size();i++){
    double val=(features[i].eval(img)==1)?1:0;
    total+=val*Fweights[i];
  }
  return total;
}


ClassifierTh::ClassifierTh(double maxF,double minD,
                       vector<Mat>& validation,vector<int>& trueVal,
                       vector<Mat>& pos,vector<Mat>& neg,
                       DetectorTh &cascade){
  double curD=0;
  double curF=1;

  vector<double> weights(pos.size()+neg.size(),0);
  for(int i=0;i<(int)pos.size();i++)
    weights[i]=1./double(2*pos.size());
  for(int i=0;i<(int)neg.size();i++)
    weights[pos.size()+i]=1./double(2*neg.size());

  vector<double> firstDistro = weights;
  normalize(firstDistro);

  threshold=0;
  int round=0;
  bool pushed=false;
  double save=0;
  while(curF>maxF){
      if(pushed){
        cascade.pop_back();
        threshold=save;
      }
      normalize(weights);
      double minErr=INFINI;
      ThFeature minF;
      #pragma omp parallel for
      for(int feature=0;feature<nbFeature;feature++){
        for(int x1=0;x1<nbTranche;x1++){
          for(int y1=0;y1<nbTranche;y1++){
            for(int x2=x1+1;x2<nbTranche;x2++){
              for(int y2=y1+1;y2<nbTranche;y2++){
                if(!(feature==2 && x2-x1<3)){
                  ThFeature feat(Feature(feature,x1,y1,x2,y2),pos,neg,weights);
                  double err=feat.error(pos,neg,weights);
                  #pragma omp critical
                  if(err<minErr){
                    minF=feat;
                    minErr=err;
                  }
                }
              }
            }
          }
        }
      }
      features.push_back(minF);
      Fweights.push_back(log((1.-minErr)/minErr));
      threshold+=0.5*Fweights[round];

      debugOut << minErr <<",";

      double val;
      #pragma omp parallel for private(val)
      for(int j=0;j<(int)pos.size();j++){
          val=(minF.eval(pos[j])==1)?0:1;
          weights[j]=weights[j]*exp(-Fweights[round]*(1.-val));
      }
      #pragma omp parallel for private(val)
      for(int j=0;j<(int)neg.size();j++){
          val=(minF.eval(neg[j])==-1)?0:1;
          weights[j+pos.size()]=weights[j+pos.size()]*exp(-Fweights[round]*(1.-val));
      }
      save=threshold;
      cascade.push_back(this);

      {
        double err=0;
        for(int j=0;j<(int)pos.size();j++){
            err+=(eval(pos[j])==1)?0:firstDistro[j];
        }

        for(int j=0;j<(int)neg.size();j++){
            err+=(eval(neg[j])==-1)?0:firstDistro[j + pos.size()];
        }
        debugOut << err << ",";
      }


      vector< pair<double,int> > sorted;
      for(int j=0;j<(int)validation.size();j++)
        sorted.push_back(pair<double,int>(evaluer(validation[j]),-trueVal[j]));
      sort(sorted.begin(),sorted.end());


      for(int j=validation.size()-1;j>=0;j--){
        if( sorted[j].second == -1 &&
            (j==0 || sorted[j-1].first!=sorted[j].first)){
          threshold=sorted[j].first;

          cascade.findD(curD,validation,trueVal);

          if(curD>=minD)
            break;
        }
      }
      cascade.findFD(curF, curD, validation ,trueVal);

      {
        double err=0;
        for(int j=0;j<(int)pos.size();j++){
            err+=(eval(pos[j])==1)?0:firstDistro[j];
        }

        for(int j=0;j<(int)neg.size();j++){
            err+=(eval(neg[j])==-1)?0:firstDistro[j + pos.size()];
        }
        debugOut << err << ",";
      }


      debugOut << curD << ","<<curF << endl;
      pushed=true;
      round++;

      printf("%d %d %d %d %d -- polarite : %d / threshold=%d   curF=%lf\
             curD=%lf  (min = %lf)\n",minF.f.numero,minF.f.x1,minF.f.y1,
                                      minF.f.x2,minF.f.y2,minF.inf?1:-1,
                                      (int)minF.threshold,curF,curD,minD);
  }

  printf("fini curF=%lf, curD=%lf \n",curF,curD);
}

void ClassifierTh::exportFD(vector<Mat>& validation,
                            vector<int>& trueVal,string file){
  FILE *out=fopen(file.c_str(),"w+");
  for(int i=0;i<(int)validation.size();i++)
    fprintf(out,"%d %lf %d\n",i,evaluer(validation[i]),trueVal[i]);
  fclose(out);
}
